package org.grill.clovercash.storage;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.grill.clovercash.accounts.Account;
import org.grill.clovercash.accounts.Split;
import org.grill.clovercash.accounts.Transaction;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class AccountWriter {
	String filename;
	Account rootAccount;
	
	private ArrayList<Reconciliation> reconciliations = new ArrayList<Reconciliation>();
	
	public AccountWriter(String filename, Account rootAccount) {
		this.filename = filename;
		this.rootAccount = rootAccount;
	}
	
	public boolean write() {
		if ("true".equals(System.getProperty("clovercash.nosave"))) {
//			System.out.println("Skipping save.");
			return true;
		}
		
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		try {
			db = dbf.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			return false;
		}
		
		doc = db.newDocument();
		Element root = doc.createElement("cloverbook");
		Element accounts = doc.createElement("accounts");
		fillAccountsList(accounts, rootAccount);
		
		root.appendChild(accounts);
		
		root.appendChild(buildTransactions());

		if (!reconciliations.isEmpty())
			root.appendChild(buildReconciliations());
		
		OutputFormat of = new OutputFormat("XML","ISO-8859-1",true);
		of.setIndent(1);
		of.setIndenting(true);
		 
		XMLSerializer serializer;
		try {
			serializer = new XMLSerializer(new FileOutputStream(filename), of);
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
			return false;
		}
		try {
			serializer.asDOMSerializer();
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
		doc.appendChild(root);
		
		try {
			serializer.serialize(doc.getDocumentElement());
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}
	
	private Node buildReconciliations() {
		Element reconciliationsElement = doc.createElement("reconciliations");
		for (Reconciliation r : reconciliations) {
			Element recElement = doc.createElement("reconciliation");
			recElement.setAttribute("date", r.getDate().toString("MM-dd-YYYY"));
			recElement.setAttribute("amount", r.getEndAmount().toPlainString());
			recElement.setAttribute("account", String.valueOf(accountMap.get(r.getAccount())));
			recElement.setAttribute("recurse", String.valueOf(r.isRecurse()));
			for (Split s : r.getSplits()) {
				Element splitElement  = doc.createElement("split");
				splitElement.setAttribute("id", splitMap.get(s));
				recElement.appendChild(splitElement);
			}

			for (StatementEntry s : r.getStatementEntries()) {
				Element entryElement = doc.createElement("entry");
				entryElement.setAttribute("date", s.date.toString("MM-dd-YYYY"));
				entryElement.setAttribute("value", s.value.toPlainString());
				entryElement.setAttribute("memo", s.memo);
				entryElement.setAttribute("payee", s.payeeName);
				recElement.appendChild(entryElement);
			}
			reconciliationsElement.appendChild(recElement);
		}
		
		return reconciliationsElement;
	}
	
	private HashMap<Split, String> splitMap = new HashMap<Split, String>();
	
	private Node buildTransactions() {
		Element transactionsElement = doc.createElement("transactions");
		int counter = 0;
		for (Transaction tx : transactions) {
			Element transElement = doc.createElement("transaction");
			transElement.setAttribute("date", tx.date.toString("MM-dd-YYYY"));
			transElement.setAttribute("description", tx.description);
			
			for (Split s : tx.splits) {
				Element splitElement = doc.createElement("split");
				splitElement.setAttribute("owner", String.valueOf(accountMap.get(s.owner)));
				splitElement.setAttribute("value", s.value.toPlainString());
				splitElement.setAttribute("reconciled", String.valueOf(s.reconciled));
				String splitId = String.valueOf(counter++);
				splitElement.setAttribute("id", splitId);
				splitMap.put(s, splitId);
				
				transElement.appendChild(splitElement);
			}
			
			transactionsElement.appendChild(transElement);
		}
		
		return transactionsElement;
	}

	private HashMap<Account, Integer> accountMap = new HashMap<Account, Integer>();
	private ArrayList<Transaction> transactions = new ArrayList<Transaction>();
	
	private int accountId = 0;
	private Document doc;

	private void fillAccountsList(Element accounts, Account a) {
		for (Account child : a.getChildren()) {
			Element childElement = doc.createElement("account");
			childElement.setAttribute("name", child.getName());
			childElement.setAttribute("type", child.getType().toString());
			childElement.setAttribute("desc", child.getDescription());
			int i = accountId++;
			childElement.setAttribute("id", String.valueOf(i));
			accountMap.put(child, i);
			for (Split s : child.getSplits())
				if (!transactions.contains(s.trans))
					transactions.add(s.trans);
			if (child.getReconciliation() != null)
				reconciliations.add(child.getReconciliation());
			
			fillAccountsList(childElement, child);
			accounts.appendChild(childElement);
		}
	}
}
